package com.zhaowb.springcloud.gate.filter;

import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import com.zhaowb.springcloud.api.vo.authority.PermissionInfo;
import com.zhaowb.springcloud.api.vo.log.LogInfo;
import com.zhaowb.springcloud.api.vo.user.UserInfo;
import com.zhaowb.springcloud.common.util.ClientUtil;
import com.zhaowb.springcloud.gate.rpc.ILogService;
import com.zhaowb.springcloud.gate.rpc.IUserService;
import com.zhaowb.springcloud.gate.util.DBLog;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.security.core.userdetails.User;
import org.springframework.session.Session;
import org.springframework.session.SessionRepository;
import org.springframework.stereotype.Component;
import org.springframework.util.Base64Utils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.net.URLEncoder;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.regex.Pattern;

/**
 * Created with IDEA
 * 权限过滤器
 * @author zhaowb
 * @date 2018/11/10 16:22
 */
@Component
public class SessionAccessFilter extends ZuulFilter {

    private static final Logger logger = LoggerFactory.getLogger(SessionAccessFilter.class);

    @Autowired
    private SessionRepository<?> repository;

    @Autowired
    private IUserService userService;

    @Autowired
    private ILogService logService;

    @Value("${gate.ignore.startWith}")
    private String startWith;

    @Value("${gate.ignore.contain}")
    private String contain;

    @Value("${gate.oauth.prefix}")
    private String oauthPrefix;

    public SessionAccessFilter() {
        super();
    }

    @Override
    public String filterType() {
        return "pre";
    }

    @Override
    public int filterOrder() {
        return 1;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() throws ZuulException {

        RequestContext ctx = RequestContext.getCurrentContext();
        HttpSession httpSession = ctx.getRequest().getSession();
        HttpServletRequest request = ctx.getRequest();
        final String requestUri = request.getRequestURI();
        final String method = request.getMethod();

        User user = getSessionUser(httpSession);
        String username = null;
        if (user != null) {
            username = user.getUsername();
            // 设置头部校验信息
            ctx.addZuulRequestHeader("Authorization", Base64Utils.encodeToString(user.getUsername().getBytes()));
        }
        // 不进行拦截的地址
        if (isStartWith(requestUri) || isContains(requestUri) || isOAuth(requestUri)) {
            return null;
        }
        List<PermissionInfo> permissionInfos = userService.getAllPermission();
        // 判断资源是否启用权限约束
        Collection<PermissionInfo> result = getPermissionInfos(requestUri, method, permissionInfos);

        if (result != null && result.size() > 0) {
            if (username != null) {
                checkAllow(requestUri, method, ctx, username);
            }
        }
        return null;
    }

    /**
     * 权限校验
     *
     * @param requestUri uri
     * @param method     method
     * @param ctx        RequestContext
     * @param username   用户名
     */
    private void checkAllow(String requestUri, String method, RequestContext ctx, String username) {
        logger.info("uri: " + requestUri + "----method: " + method);
        List<PermissionInfo> permissionInfos = getPermissionInfos(ctx.getRequest(), username);
        Collection<PermissionInfo> result = getPermissionInfos(requestUri, method, permissionInfos);
        if (result.size() <= 0) {
            setFailedRequest("403 Forbidden!", 403);
        } else {
            PermissionInfo[] pms = result.toArray(new PermissionInfo[]{});
            PermissionInfo pm = pms[0];
            if (!method.equals("GET")) {
                setCurrentUserInfoAndLog(ctx, username, pm);
            }
        }

    }

    /**
     * 记录日志信息
     *
     * @param ctx
     * @param username 用户名
     * @param pm       权限信息
     */
    private void setCurrentUserInfoAndLog(RequestContext ctx, String username, PermissionInfo pm) {
        UserInfo userInfo = userService.getUserByUsername(username);
        String host = ClientUtil.getClientIp(ctx.getRequest());
        ctx.addZuulRequestHeader("userId", userInfo.getId());
        ctx.addZuulRequestHeader("userName", URLEncoder.encode(userInfo.getUsername()));
        ctx.addZuulRequestHeader("userHost", host);
        LogInfo logInfo = new LogInfo(pm.getMenu(), pm.getName(), pm.getUri(), new Date(), userInfo.getId(), userInfo.getName(), host);
        DBLog.getInstance().setLogService(logService).offerQueue(logInfo);
    }

    /**
     * 报告响应正文和代码的错误消息
     *
     * @param body 正文。
     * @param code 错误代码
     */
    private void setFailedRequest(String body, int code) {
        logger.info("Reporting error ({}): {}", code, body);
        RequestContext ctx = RequestContext.getCurrentContext();
        ctx.setResponseStatusCode(code);
        if (ctx.getResponseBody() == null) {
            ctx.setResponseBody(body);
            ctx.setSendZuulResponse(false);
            throw new RuntimeException("Code : " + code + ",body :" + body);
        }
    }


    /**
     * 获取权限
     *
     * @param request
     * @param username 用户名
     * @return
     */
    private List<PermissionInfo> getPermissionInfos(HttpServletRequest request, String username) {
        List<PermissionInfo> permissionInfos;
        if (request.getSession().getAttribute("permission") == null) {
            permissionInfos = userService.getPermissionByUsername(username);
            request.getSession().setAttribute("permission", permissionInfos);
        } else {
            permissionInfos = (List<PermissionInfo>) request.getSession().getAttribute("permission");
        }
        return permissionInfos;
    }

    /**
     * 获取目标权限资源
     *
     * @param requestUri      请求uri
     * @param method          方法
     * @param permissionInfos 带筛选资源信息
     * @return
     */
    private Collection<PermissionInfo> getPermissionInfos(String requestUri, String method, List<PermissionInfo> permissionInfos) {
        return Collections2.filter(permissionInfos, new Predicate<PermissionInfo>() {
            @Override
            public boolean apply(PermissionInfo permissionInfo) {
                String url = permissionInfo.getUri();
                String uri = url.replaceAll("\\{\\*\\}", "[a-zA-Z\\\\d]+");
                String regEx = "^" + uri + "$";
                return (Pattern.compile(regEx).matcher(requestUri).find() || requestUri.startsWith(url + "/"))
                        && method.equals(permissionInfo.getMethod());
            }
        });
    }

    /**
     * 判断是否为oauth 资源
     *
     * @param requestUri 请求uri
     * @return
     */
    private boolean isOAuth(String requestUri) {
        return requestUri.startsWith(oauthPrefix);
    }

    /**
     * 是否包含某种特征
     *
     * @param requestUri uri
     * @return
     */
    private boolean isContains(String requestUri) {
        for (String s : contain.split(",")) {
            if (requestUri.equals(s)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 判断uri 以什么开头
     *
     * @param requestUri 请求uri
     * @return
     */
    private boolean isStartWith(String requestUri) {
        for (String s : startWith.split(",")) {
            if (requestUri.equals(s)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 获取session 中的用户信息
     *
     * @param httpSession
     * @return
     */
    private User getSessionUser(HttpSession httpSession) {
        Session session = repository.getSession(httpSession.getId());
        if (httpSession.getAttribute("SPRING_SECURITY_CONTEXT") == null) {
            return null;
        }
        SecurityContextImpl securityContext = (SecurityContextImpl) httpSession.getAttribute("SPRING_SECURITY_CONTEXT");
        return (User) securityContext.getAuthentication().getPrincipal();
    }
}
